#pragma once

#include "Error.h"
#include "ErrorUtils.h"
#include "NonCopyable.h"

namespace TestFramework
{
    template <class T>
    class VectorProcessor : NonCopyable
    {
    public:
        virtual void ProcessItem(T& item) = 0;
    };

    template <class T>
    class VectorComparer : NonCopyable
    {
    public:
        virtual bool Compare(const T& lhs, const T& rhs) const = 0;
    };

    template <class T>
    class TestVector
    {
    public:
        typedef T* Iterator;
        typedef const T* ConstIterator;
        typedef size_t SizeType;
        typedef T ValueType;
        typedef const T ConstValueType;

        TestVector()
            : MaxSize(0)
            , Size(0)
            , Array(0)
        {
        }

        explicit TestVector(SizeType size)
            : MaxSize(size > 0 ? size : 1)
            , Size(0)
            , Array(new T[MaxSize])
        {
        }

        ~TestVector()
        {
            delete [] Array;
        }

        ValueType& operator[](SizeType index)
        {
            CheckRange(index);
            if (Size <= index)
            {
                Size = index + 1;
            }

            return Array[index];
        }

        ConstValueType& operator[](SizeType index) const
        {
            CheckRange(index);
            return Array[index];
        }

        bool operator==(const TestVector& rhs) const
        {
            if (GetSize() != rhs.GetSize())
            {
                return false;
            }

            ConstIterator iter = Begin();
            ConstIterator iterRhs = rhs.Begin();
            for (; iter != End(); ++iter, ++iterRhs)
            {
                if (*iter != *iterRhs)
                {
                    return false;
                }
            }

            return true;
        }

        void PushBack(T&& item)
        {
            IncreaseVectorSizeIfNeed();
            Array[Size++] = item;
        }

        void PushBack(T& item)
        {
            IncreaseVectorSizeIfNeed();
            Array[Size++] = item;
        }

        void Reserve(SizeType newSize)
        {
            if (newSize > MaxSize)
            {
                IncreaseVectorSize(newSize);
            }
        }

        Iterator Insert(Iterator iter, const T& item)
        {
            SizeType index = iter - Begin();
            MoveItemsForward(index);
            Array[index] = item;
            return Begin() + index;
        }

        void Clear()
        {
            delete [] Array;
            Array = new T[DefaultArraySize];
            Size = 0;
        }

        void Remove(const T& object)
        {
            Iterator iter = Begin();
            for (; iter < End(); ++iter)
            {
                if (*iter == object)
                {
                    iter->~T();
                    MoveItemsBack(iter - Begin());
                    if (iter != End())
                    {
                        End()->~T();
                    }
                }
            }
        }

        void RemoveAt(SizeType index)
        {
            CheckRange(index);
            Iterator iter = Begin() + index;
            iter->~T();
            MoveItemsBack(iter - Begin());
            if (iter != End())
            {
                End()->~T();
            }
        }

        void Sort(const VectorComparer<T>& comparer)
        {
            Iterator iter = Begin();
            for (; iter != (End() - 1); ++iter)
            {
                for (Iterator subIter = iter + 1; subIter != End(); ++subIter)
                {
                    if (comparer.Compare(*iter, *subIter))
                    {
                        T value = *iter;
                        *iter = *subIter;
                        *subIter = value;
                    }
                }
            }
        }

        void Swap(TestVector<T>& rhs)
        {
            if (this != &rhs)
            {
                SizeType tmpMaxSize = MaxSize;
                SizeType tmpSize = Size;
                T* tmpArray = Array;
                MaxSize = rhs.MaxSize;
                Size = rhs.Size;
                Array = rhs.Array;
                rhs.MaxSize = tmpMaxSize;
                rhs.Size = tmpSize;
                rhs.Array = tmpArray;
            }
        }

        void Enumerate(VectorProcessor<T>& processor)
        {
            for (Iterator iter = Begin(); iter != End(); ++iter)
            {
                processor.ProcessItem(*iter);
            }
        }

        SizeType GetSize() const
        {
            return Size;
        }

        SizeType GetCapacity() const
        {
            return MaxSize;
        }

        bool IsEmpty() const
        {
            return Size == 0;
        }

        Iterator Begin()
        {
            return &Array[0];
        }

        ConstIterator Begin() const
        {
            return &Array[0];
        }

        Iterator End()
        {
            return &Array[GetSize()];
        }

        ConstIterator End() const
        {
            return &Array[GetSize()];
        }

    private:
        static const int DefaultArraySize = 10;

        void MoveItemsForward(SizeType startIndex)
        {
            if (Size < MaxSize)
            {
                SizeType current = Size;
                while (current != startIndex)
                {
                    Array[current] = Array[current - 1];
                    --current;
                }

                Size++;
            }
            else
            {
                IncreaseVectorSize();
                MoveItemsForward(startIndex);
            }
        }

        void MoveItemsBack(SizeType startIndex)
        {
            SizeType index;
            Iterator subIter = Begin() + startIndex;
            while (subIter!= (End() - 1))
            {
                index = subIter - Begin();
                Array[index] = Array[index + 1];
                ++subIter;
            }

            --Size;
        }

        void CheckRange(SizeType index) const
        {
            if (index >= MaxSize)
            {
              throw std::exception(FormatErrorMessage("Out of range! Element with index '{0}' not found.", index), Errors::ERROR_OUT_OF_RANGE);
            }
        }

        void IncreaseVectorSizeIfNeed()
        {
            if (Size >= MaxSize)
            {
                IncreaseVectorSize();
            }
        }

        void IncreaseVectorSize(SizeType newSize = 0)
        {
            if (newSize)
            {
                MaxSize = newSize;
            }
            else
            {
                MaxSize += 1;
                MaxSize = static_cast<SizeType>(MaxSize * (MaxSize > 10 ? 1.5 : 2));
            }

            T* tmpArray = new T[MaxSize];
            Iterator iter = Begin();
            for (int i = 0; iter != End(); ++iter, ++i)
            {
                tmpArray[i] = *iter;
            }

            delete [] Array;
            Array = tmpArray;
        }

    private:
        SizeType MaxSize;
        SizeType Size;
        T* Array;
    };
}
